function bootstrap_product () {
  $( 'product'               ).on ( 'change', product_changed        );
  $( 'sort_components_table' ).on ( 'click',  sort_components_table  );
  $( 'pull_integration'      ).on ( 'click',  pull_integration       );
  $( 'pull_specification'    ).on ( 'click',  pull_specification     );
  $( 'pull_dfs'              ).on ( 'click',  pull_dfs               );
  $( 'no_dstp'               ).on ( 'change', no_dstp_changed        );
  $( 'no_dfs'                ).on ( 'change', no_dfs_changed         );
  $( 'no_description'        ).on ( 'change', no_description_changed );
/*$( 'export_csv'            ).on ( 'click',  export_csv             );*/
  
  var parameter = window.location.search.toQueryParams ();
  
  if ( parameter['id'] ) {
    $( 'product' ).value = parameter['id'];
    
    refresh_page ();
  }
}

function refresh_page () {
  product_changed ();
}

function product_changed () {
  $( 'component_table_wrapper'    ).replace ( '<div id="component_table_wrapper" />'    );
  $( 'history_table_wrapper'      ).replace ( '<div id="history_table_wrapper" />'      );
  $( 'bugzilla_bug_table_wrapper' ).replace ( '<div id="bugzilla_bug_table_wrapper" />' );
  $( 'isock_altnavigation'        ).replace ( '<div id="isock_altnavigation" />'        );
  
  display_components ( scope, 'with_bugzilla_bugs', $( 'no_dstp' ).checked ? null : 'with_dstp_issues' );
}

function sort_components_table () {
  display_components ( scope );
}

function pull_integration   () { pull ( $( 'product' ).value, 'integration',   'no_update' ); }
function pull_specification () { pull ( $( 'product' ).value, 'specification', 'no_update' ); }
function pull_dfs           () { pull ( $( 'product' ).value, 'dfs',           'no_update' ); }

function no_dstp_changed () {
  var product = $( 'product' ).value;
  
  if ( product != '' ) {
    display_components ( scope, 'with_bugzilla_bugs', $( 'no_dstp' ).checked ? null : 'with_dstp_issues' );
  }
}

function no_dfs_changed () {
  if ( $( 'no_dfs' ).checked ) {
    $$( '.dfs_column' ).each ( function ( node ) { node.style.display = 'none' } );
  }
  else {
    $$( '.dfs_column' ).each ( function ( node ) { node.style.display = ''     } );
  }
}

function td_class ( lhs, rhs, more ) {
  var klass = ( lhs && rhs ) ? ( lhs.content == rhs.content ? '' : 'error' ) : ( lhs || rhs ? 'warning' : '' );
  
  if ( more ) {
    klass = klass + " " + more
  }
  
  return klass ? ' class="' + klass + '"' : '';
}

function td_class_2 ( integration_v, specification_v, last_v ) {
  if ( integration_v && specification_v && last_v ) {
    return integration_v.content == last_v.content && specification_v.content == last_v.content ? '' : ' class="warning"';
  }
  if ( integration_v &&                    last_v ) {
    return integration_v.content == last_v.content                                              ? '' : ' class="warning"';
  }
  if (                  specification_v && last_v ) {
    return                                            specification_v.content == last_v.content ? '' : ' class="warning"';
  }
  
  return '';
}

function hash_to_tag ( h ) {
  return ( h && h.tag && h.content ) ?
      "<" + h.tag
    +   ( h.id    ? " id=\""    + h.id    + "\"" : "" )
    +   ( h.class ? " class=\"" + h.class + "\"" : "" )
    +   ( h.title ? " title=\"" + h.title + "\"" : "" )
    +   ( h.href  ? " href=\""  + h.href  + "\"" : "" )
    + ">"
    + h.content
    + "</" + h.tag + ">"
  : '-'
  ;
}

function maybe_dash ( s ) {
  return s || '-';
}

function display_components ( scope, with_bugzilla_bugs, with_dstp_issues ) {
  var product = $( 'product' ).value;
  
  var button_ids = $w( 'sort_components_table pull_specification pull_integration pull_dfs no_update export_xls export_csv' ); /* pull_specification pull_integration pull_dfs */

  if ( product != '' ) {
    new Ajax.Request ( '/rest/product/' + product + '/stage', {
      method         : 'get',
      requestHeaders : { Accept : 'application/json' },
      onSuccess      : function ( response ) {
        var stage = response.responseJSON.stage;

        $( 'no_dfs' ).checked = ( !stage || stage == 'DR0' || stage == 'DR1' || stage == 'DR2' );
      }
    } );
    
    new Ajax.Request ( '/rest/user/' + $( 'user_id' ).value + '/authorization/tinkerer' + '/product/' + product, {
      method         : 'get',
      requestHeaders : { Accept : 'application/json' },
      onSuccess      : function ( response ) {
        button_ids.grep ( /^(pull_|no_update|export_)/ ).each ( function ( id ) { enable ( id ) } );
      }
    } );
    
    new Ajax.Request ( '/rest/user/' + $( 'user_id' ).value, {
      method         : 'get',
      requestHeaders : { Accept : 'application/json' },
      onSuccess      : function ( response ) {
        if ( response.responseJSON.role.can_bofh ) {
          button_ids.grep ( /^(pull_|no_update|export_)/ ).each ( function ( id ) { enable ( id ) } );
        }
      }
    } );
    
    button_ids.grep ( /^sort_/ ).each ( function ( id ) { enable ( id ) } );
    
    new Ajax.Request ( '/rest/product/' + product + '/components', {
      method         : 'get',
      requestHeaders : { Accept : 'application/json' },
      onSuccess      : function ( response ) {
        var components = response.responseJSON;

        var html =
          "<div id=\"component_table_wrapper\">\n"
        + "  <table id=\"component_table\" class=\"components sortable\">\n"
        + "    <caption>Components</caption>\n"
        + "    <thead>\n"
        + "      <tr>\n"
        + "        <th id=\"specification_name\"       class=\"noedit\"                   >Spec. Name</th>\n"
        + "        <th id=\"specification_version\"    class=\"noedit nosort\"            >Spec. Version</th>\n"
      /*+ "        <th id=\"specification_changed\"    class=\"noedit\"                   >Spec. Changed</th>\n"*/
        + "        <th id=\"integration_name\"         class=\"noedit\"                   >Int. Name</th>\n"
        + "        <th id=\"integration_version\"      class=\"noedit nosort\"            >Int. Version</th>\n"
      /*+ "        <th id=\"integration_changed\"      class=\"noedit\"                   >Int. Changed</th>\n"*/
        + "        <th id=\"dfs_name\"                 class=\"noedit dfs_column\"        >DFS Name</th>\n"
        + "        <th id=\"dfs_version\"              class=\"noedit nosort dfs_column\" >DFS Version</th>\n"
      /*+ "        <th id=\"dfs_changed\"              class=\"noedit dfs_column\"        >DFS Changed</th>\n"*/
        + "        <th id=\"component_type\"           class=\"noedit sortasc\"           >Type</th>\n"
        + "        <th id=\"bug_tracker_last_version\" class=\"noedit nosort\"            >Last Version</th>\n"
        + "        <th id=\"component_description\"    class=\"noedit\"                   >Description</th>\n"
        + "      </tr>\n"
        + "    </thead>\n"
        + "    <tfoot>\n"
        + "      <tr>\n"
        + "        <th                                                                    >Spec. Name</th>\n"
        + "        <th                                                                    >Spec. Version</th>\n"
      /*+ "        <th                                                                    >Spec. Changed</th>\n"*/
        + "        <th                                                                    >Int. Name</th>\n"
        + "        <th                                                                    >Int. Version</th>\n"
      /*+ "        <th                                                                    >Int. Changed</th>\n"*/
        + "        <th                                 class=\"dfs_column\"               >DFS Name</th>\n"
        + "        <th                                 class=\"dfs_column\"               >DFS Version</th>\n"
      /*+ "        <th                                 class=\"dfs_column\"               >DFS Changed</th>\n"*/
        + "        <th                                                                    >Type</th>\n"
        + "        <th                                                                    >Last Version</th>\n"
        + "        <th                                                                    >Description</th>\n"
        + "      </tr>\n"
        + "    </tfoot>\n"
        + "    <tbody>\n"
        ;

        var i = 1;

        components.each ( function ( h ) {
          if ( h.component_type != 'datasheet' ) {
            html = html
            + "      <tr class=\"" + ( i % 2 ? "odd" : "even" ) + "\">\n"
            + "        <td" + td_class   ( h.specification_name,    h.integration_name                                  ) + ">" + maybe_dash  ( h.specification_name       ) + "</td>\n"
            + "        <td" + td_class   ( h.specification_version, h.integration_version                               ) + ">" + hash_to_tag ( h.specification_version    ) + "</td>\n"
          /*+ "        <td>"                                                                                              +       maybe_dash  ( h.specification_changed    ) + "</td>\n"*/
            + "        <td" + td_class   ( h.integration_name,      h.specification_name                                ) + ">" + maybe_dash  ( h.integration_name         ) + "</td>\n"
            + "        <td" + td_class   ( h.integration_version,   h.specification_version                             ) + ">" + hash_to_tag ( h.integration_version      ) + "</td>\n"
          /*+ "        <td>"                                                                                              +       maybe_dash  ( h.integration_changed      ) + "</td>\n"*/
            + "        <td" + td_class   ( h.dfs_name,              h.integration_name,    'dfs_column'                 ) + ">" + maybe_dash  ( h.dfs_name                 ) + "</td>\n"
            + "        <td" + td_class   ( h.dfs_version,           h.integration_version, 'dfs_column'                 ) + ">" + hash_to_tag ( h.dfs_version              ) + "</td>\n"
          /*+ "        <td>"                                                                                                    + maybe_dash  ( h.dfs_changed              ) + "</td>\n"*/
            + "        <td>"                                                                                                    + maybe_dash  ( h.component_type           ) + "</td>\n"
            + "        <td" + td_class_2 ( h.integration_version,   h.specification_version, h.bug_tracker_last_version ) + ">" + hash_to_tag ( h.bug_tracker_last_version ) + "</td>\n"
            + "        <td>"                                                                                                    + maybe_dash  ( h.component_description    ) + "</td>\n"
            + "      </tr>\n"
            ;

            i = i + 1;
          }
        } );

        html = html
        + "    </tbody>\n"
        + "  </table>\n"
        + "  <a type=\"application/vnd.ms-excel\" href=\"/tmp/" + $( 'session_id' ).value + "/" + $( 'product' ).value + "-components.xls\">XLS</a>\n"
        + "  </div>\n"
        ;

        if ( components.length > 0 ) {
          $( 'component_table_wrapper' ).replace ( html );
          
          no_dfs_changed ();
          
          TableKit.Sortable.init ( 'component_table' );
          TableKit.reload ();
        }
        else {
          $( 'component_table_wrapper' ).replace ( '<div id="component_table_wrapper"><p class="error">No components!</p></div>' );
        }
      }
    } );

    display_history ();

    if ( with_bugzilla_bugs != null ) {
      display_bugzilla_bugs ( scope, with_dstp_issues );
    }
  }
  else {
    button_ids.each ( function ( id ) { disable ( id ) } );
    
    $w( 'component history bugzilla_bug dstp_issue' ).each ( function ( stem ) { var id = stem + "_table_wrapper" ; $( id ).replace ( "<div id=\"" + id + "\"></div>" ) } );
    
    $( 'isock_altnavigation' ).replace ( '<div id="isock_altnavigation" />' );
  }
}

function display_history () {
  var product = $( 'product' ).value;
  
  if ( product != '' ) {
    new Ajax.Request ( '/rest/product/' + product + '/history', {
      method         : 'get',
      requestHeaders : { Accept : 'application/json' },
      onSuccess      : function ( response ) {
        var components = response.responseJSON;

        var html =
          "<div id=\"history_table_wrapper\" class=\"more_down_wrapper\">\n"
        + "  <div class=\"more_down_a\">\n"
        + "    <a id=\"history_table_more_down\" class=\"more_down\" onclick=\"more_down ( 'history_table' )\">History table</a>\n"
        + "  </div>\n"
        + "  <table id=\"history_table\" class=\"history sortable more_down_content\">\n"
        + "    <caption>History</caption>\n"
        + "    <thead>\n"
        + "      <tr>\n"
        + "        <th id=\"last_update\"       class=\"date-iso\">When</th>\n"
        + "        <th id=\"scope_id\"          class=\"\"        >Scope</th>\n"
        + "        <th id=\"component_id\"      class=\"\"        >ID</th>\n"
        + "        <th id=\"component_version\" class=\"nosort\"  >Version</th>\n"
        + "        <th id=\"component_type\"    class=\"\"        >Type</th>\n"
        + "      </tr>\n"
        + "    </thead>\n"
        + "    <tfoot>\n"
        + "      <tr>\n"
        + "        <th                                            >When</th>\n"
        + "        <th                                            >Scope</th>\n"
        + "        <th                                            >ID</th>\n"
        + "        <th                                            >Version</th>\n"
        + "        <th                                            >Type</th>\n"
        + "      </tr>\n"
        + "    </tfoot>\n"
        + "    <tbody>\n"
        ;

        var i = 0;
        var last_update = 0;

        components.each ( function ( h ) {
          if ( h.last_update != last_update ) {
            i = i + 1;
            last_update = h.last_update;
          }

          html = html
          + "      <tr class=\"" + ( i % 2 ? "odd" : "even" ) + "\">\n"
          + "        <td>" + ( h.last_update       || ''  ) + "</td>\n"
          + "        <td>" + ( h.scope_id          || ''  ) + "</td>\n"
          + "        <td>" + ( h.component_id      || ''  ) + "</td>\n"
          + "        <td>" + ( h.component_version || ''  ) + "</td>\n"
          + "        <td>" + ( h.component_type    || ''  ) + "</td>\n"
          + "      </tr>\n"
          ;
        } );

        html = html
        + "    </tbody>\n"
        + "  </table>\n"
        + "  <div class=\"more_back_a\">\n"
        + "    <a id=\"history_table_more_back\" class=\"more_back\" onclick=\"more_back ( 'history_table' )\">History table</a>\n"
        + "  </div>\n"
        + "</div>\n"
        ;

        if ( components.length > 0 ) {
          $( 'history_table_wrapper' ).replace ( html );

          TableKit.Sortable.init ( 'history_table' );
          TableKit.reload ();
        }
        else {
          $( 'history_table_wrapper' ).replace ( '<div id="history_table_wrapper" />' );
        }
      }
    } );
  }
  else {
    $( 'history_table_wrapper' ).replace ( '<div id="history_table_wrapper" />' );
  }
}

function bugzilla_bug_table_alt_navigation ( bugs ) {
  var html =
    "  <ul id=\"isock_altnavigation-index\">\n"
  + "    <li id=\"isock_altnavigation-index-toc\">Bugs</li>\n"
  + "    <li>Bugs</li>\n"
  ;
  
  var at_least_one_row = 0;
  var seen = {};
  
  bugs.each ( function ( h ) {
    var name = h.name.content;
    
    if ( ! seen[name] ) {
      var klass = name + "-bugs";
      
      html = html
      + "    <li><a href=\"#" + klass + "\" onclick=\"highlite_rows ( '" + klass + "' )\">" + name + "</a></li>\n"
      ;

      seen[name] = 1;

      at_least_one_row = 1;
    }
  } );
  
  html = html
  + "  </ul>\n"
  ;
  
  return at_least_one_row ? html : "";
}

function highlite_rows ( klass ) {
  var color = '#9932CC'; /* Dark Orchid: 9932CC, Orchid: DA70D6, Fuchsia: FF00FF, Maroon (W3C): 7F0000, Midnight Blue: 191970 */
  
  $$( 'table#bugzilla_bug_table tr' ).each ( function ( node ) {
    node.style.borderLeftStyle  = '';
    node.style.borderRightStyle = '';
    node.style.borderLeftColor  = '';
    node.style.borderRightColor = '';
    node.style.borderLeftWidth  = '';
    node.style.borderRightWidth = '';
  } );
  
  $$( 'table#bugzilla_bug_table tr.' + klass ).each ( function ( node ) {
    node.style.borderLeftStyle  = 'solid';
    node.style.borderRightStyle = 'solid';
    node.style.borderLeftColor  = color;
    node.style.borderRightColor = color;
    node.style.borderLeftWidth  = '.5em';
    node.style.borderRightWidth = '.5em';
  } );
}

function bugzilla_bug_table ( bugs ) {
  var html =
    "  <table id=\"bugzilla_bug_table\" class=\"bugs sortable\">\n"
  + "    <caption>Bugs</caption>\n"
  + "    <thead>\n"
  + "      <tr>\n"
  + "        <th colspan=\"3\">Component</th>\n"
  + "        <th colspan=\"8\">Bug</th>\n"
  + "      </tr>\n"
  + "      <tr class=\"column_ids\">\n"
  + "        <th id=\"component_name\"    class=\"noedit\"        >ID</th>\n"
  + "        <th id=\"component_version\" class=\"noedit nosort\" >Version</th>\n"
  + "        <th id=\"component_type\"    class=\"noedit sortasc\">Type</th>\n"
  + "        <th id=\"bug_id\"            class=\"noedit\"        >ID</th>\n"
  + "        <th id=\"bug_category\"      class=\"noedit\"        >Category</th>\n"
  + "        <th id=\"bug_status\"        class=\"noedit\"        >Status</th>\n"
  + "        <th id=\"bug_resolution\"    class=\"noedit\"        >Resolution</th>\n"
  + "        <th id=\"bug_milestone\"     class=\"noedit\"        >Milestone</th>\n"
  + "        <th id=\"bug_severity\"      class=\"noedit\"        >Severity</th>\n"
  + "        <th id=\"bug_priority\"      class=\"noedit\"        >Priority</th>\n"
  + "        <th id=\"bug_description\"   class=\"noedit nosort\" >Summary<span class=\"with_description\"> + Description</span></th>\n"
  + "      </tr>\n"
  + "    </thead>\n"
  + "    <tfoot>\n"
  + "      <tr>\n"
  + "        <th                                                 >ID</th>\n"
  + "        <th                                                 >Version</th>\n"
  + "        <th                                                 >Type</th>\n"
  + "        <th                                                 >ID</th>\n"
  + "        <th                                                 >Category</th>\n"
  + "        <th                                                 >Status</th>\n"
  + "        <th                                                 >Resolution</th>\n"
  + "        <th                                                 >Milestone</th>\n"
  + "        <th                                                 >Severity</th>\n"
  + "        <th                                                 >Priority</th>\n"
  + "        <th                                                 >Summary<span class=\"with_description\"> + Description</span></th>\n"
  + "      </tr>\n"
  + "      <tr>\n"
  + "        <th colspan=\"3\">Component</th>\n"
  + "        <th colspan=\"8\">Bug</th>\n"
  + "      </tr>\n"
  + "    </tfoot>\n"
  + "    <tbody>\n"
  ;

  var i = 1;
  var at_least_one_row = 0;
  var seen = {};

  bugs.each ( function ( h ) {
    var tr_class = h.name.content + "-bugs";
    
    html = html
    + "      <tr class=\"" + ( i % 2 ? "odd" : "even" ) + " " + tr_class + "\"" + ( seen[h.name.content] ? "" : " id=\"" + tr_class + "\"" ) + ">\n"
    + "        <td>" + ( h.name.href    ? "<a href=\"" + h.name.href    + "\"" + ( h.name.title    ? " title=\"" + h.name.title    + "\"" : "" ) + ">" + h.name.content    + "</a>" : h.name.content                  ) + "</td>\n"
    + "        <td>" + ( h.version.href ? "<a href=\"" + h.version.href + "\"" + ( h.version.title ? " title=\"" + h.version.title + "\"" : "" ) + ">" + h.version.content + "</a>" : h.version.content               ) + "</td>\n"
    + "        <td>" +                                                                                                                                                                h.type                            + "</td>\n"
    + "        <td>" + ( h.id.href      ? "<a href=\"" + h.id.href      + "\"" + ( h.id.title      ? " title=\"" + h.id.title      + "\"" : "" ) + ">" + h.id.content      + "</a>" : h.id.content                    ) + "</td>\n"
    + "        <td>" +                                                                                                                                                                h.category                        + "</td>\n"
    + "        <td"  +                                                                                           ( h.status     ? " class=\"" + h.status.toLowerCase ()     + "\">" + h.status.beautify ()     : ">-" ) + "</td>\n"
    + "        <td"  +                                                                                           ( h.resolution ? " class=\"" + h.resolution.toLowerCase () + "\">" + h.resolution.beautify () : ">-" ) + "</td>\n"
    + "        <td>" +                                                                                                                                                                h.milestone                       + "</td>\n"
    + "        <td>" +                                                                                                                                                                h.severity.beautify ()            + "</td>\n"
    + "        <td>" +                                                                                                                                                                h.priority.beautify ()            + "</td>\n"
    + "        <td>\n"
    + "          <p class=\"summary\" title=\"" + h.comment.escapeHTML () + "\">" + h.description.escapeHTML () + "</p>\n"
    + "          <textarea class=\"description with_description\" readonly=\"readonly\">" + h.comment.escapeHTML () + "</textarea>\n"
    + "        </td>\n"
    + "      </tr>\n"
    ;

    seen[h.name.content] = 1;

    at_least_one_row = 1;

    i = i + 1;
  } );

  html = html
  + "    </tbody>\n"
  + "  </table>\n"
  ;
  
  return at_least_one_row ? html : "";
}

function display_bugzilla_bugs ( scope, with_dstp_issues ) {
  var product = $( 'product' ).value;
  
  if ( product != '' ) {
    new Ajax.Request ( '/rest/product/' + product + '/bugs?tracker=bugzilla&scope=' + scope, {
      method         : 'get',
      requestHeaders : { Accept : 'application/json' },
      onCreate       : function () {
        display_progress ( 'Querying BZ...' );
      },
      onSuccess      : function ( response ) {
        var bugs = response.responseJSON;

        var html =
          "<div id=\"bugzilla_bug_table_wrapper\">\n"
        + bugzilla_bug_table ( bugs )
        + "</div>\n"
        ;
        
        var an_html =
          "<div id=\"isock_altnavigation\">\n"
        + bugzilla_bug_table_alt_navigation ( bugs )
        + "</div>\n"
        ;

        if ( bugs.length > 0 ) {
          $( 'bugzilla_bug_table_wrapper' ).replace (    html );
          $( 'isock_altnavigation'        ).replace ( an_html );

          no_description_changed ();

          TableKit.Sortable.init ( 'bugzilla_bug_table' );
          TableKit.reload ();
        }
        else {
          $( 'bugzilla_bug_table_wrapper' ).replace ( '<div id="bugzilla_bug_table_wrapper"><p class="error">No bugzilla bugs!</p></div>' );
          $( 'isock_altnavigation'        ).replace ( '<div id="isock_altnavigation" />'                                                  );
        }
      },
      onFailure      : function () {
        $( 'bugzilla_bug_table_wrapper' ).replace ( '<div id="bugzilla_bug_table_wrapper" />' );
        $( 'isock_altnavigation'        ).replace ( '<div id="isock_altnavigation" />'        );
      },
      onComplete     : function () {
        hide_progress ();

        if ( with_dstp_issues != null ) {
          display_dstp_issues ( scope );
        }
      }
    } );
  }
  else {
    $( 'bugzilla_bug_table_wrapper' ).replace ( '<div id="bugzilla_bug_table_wrapper" />' );
    $( 'isock_altnavigation'        ).replace ( '<div id="isock_altnavigation" />'        );
  }
}

function display_dstp_issues ( scope ) {
  var product = $( 'product' ).value;
  
  if ( product != '' ) {
    new Ajax.Request ( '/rest/product/' + product + '/bugs?tracker=DSTP&scope=' + scope, {
      method         : 'get',
      requestHeaders : { Accept : 'application/json' },
      onCreate       : function () {
        display_progress ( 'Querying DSTP...' );
      },
      onSuccess      : function ( response ) {
        var issues = response.responseJSON;

        if ( issues.length > 0 ) {
          $( 'dstp_issue_table_wrapper' ).replace ( "<div id=\"dstp_issue_table_wrapper\">" + issues_as_html_table ( issues, 'dstp_issue_table' ) + "</div>" );

          no_description_changed ();

          TableKit.Sortable.init ( 'dstp_issue_table' );
          TableKit.reload ();
        }
        else {
          $( 'dstp_issue_table_wrapper' ).replace ( '<div id="dstp_issue_table_wrapper"><p class="error">No DSTP issues!</p></div>' );
        }
      },
      onFailure      : function () {
        $( 'dstp_issue_table_wrapper' ).replace ( '<div id="dstp_issue_table_wrapper" />' );
      },
      onComplete     : function () {
        hide_progress ();
      }
    } );
  }
  else {
    $( 'dstp_issue_table_wrapper' ).replace ( '<div id="dstp_issue_table_wrapper" />' );
  }
}

function tr_as_csv ( tr ) {
  return tr.select ( 'td' ).map ( function ( td ) {
    var a_tc = td.select ( 'a'         ).map ( function ( n ) { return n.innerHTML; } ).join ( '' );
    var p_tc = td.select ( 'p.summary' ).map ( function ( n ) { return n.innerHTML; } ).join ( '' );

    return '"' + ( a_tc || p_tc || td.innerHTML ) + '"';
  } ).join ( ',' ) + "\n";
}

function export_csv () {
  alert ( $$( 'table#bugzilla_bug_table tbody tr' ).map ( function ( tr ) { return tr_as_csv ( tr ); } ).join ( '' ) );
}
